package com.su.sweet.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.IdUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.su.sweet.constants.AuditFlagEnum;
import com.su.sweet.constants.DecisionEnum;
import com.su.sweet.constants.ExtConstants;
import com.su.sweet.exception.ServiceException;
import com.su.sweet.mapper.ActExtAuditMapper;
import com.su.sweet.mapper.ActExtFlowLogMapper;
import com.su.sweet.mapper.ActExtTodoMapper;
import com.su.sweet.model.*;
import com.su.sweet.service.ActExtBindService;
import com.su.sweet.service.ActUserService;
import com.su.sweet.service.FlowRunService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.solon.annotation.Db;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.Task;
import org.noear.redisx.RedisClient;
import org.noear.redisx.plus.RedisBus;
import org.noear.snack.ONode;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
import org.noear.solon.data.annotation.Tran;
import org.noear.solon.data.cache.CacheService;
import org.noear.solon.data.tran.TranPolicy;

import java.math.BigInteger;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

@Slf4j
@Component
public class FlowRunServiceImpl implements FlowRunService {
    @Db("master")
    private ActExtAuditMapper actExtAuditMapper;

    @Db("master")
    private ActExtFlowLogMapper actExtFlowLogMapper;

    @Db("master")
    private ActExtTodoMapper actExtTodoMapper;

    @Inject
    private CacheService cacheService;

    @Inject
    private RedisClient redisClient;

    @Inject
    private ActExtBindService actExtBindService;

    @Inject
    private ActUserService actUserService;

    @Inject
    protected RuntimeService runtimeService;

    @Inject
    private TaskService taskService;

    @Tran
    @Override
    public List<AuditData> startTask(AuditTask task) {
        List<AuditData> dataList = task.getDataList();
        ProgressInfo progress = new ProgressInfo(task.getTaskId(), dataList.size());
        cacheService.store(progress.getId(), progress, 600);

        String definitionId = actExtBindService.selectActFlowId(task.getAreaCode(), task.getDataType());
        if (StringUtils.isEmpty(definitionId)) {
            throw new ServiceException("flow start error: Please bind a workflow");
        }

        AtomicInteger atom = new AtomicInteger(0);
        for (AuditData data : dataList) {
            ProcessInstance pi = runtimeService.startProcessInstanceById(definitionId, data.getParams());
            if (pi.isEnded()) {
                data.setWorkFlowId(pi.getId());
                // 设置数据状态已完成
                data.setAuditFlag(AuditFlagEnum.DEPLOY.getVal());
                // 插入数据审核状态表
                BigInteger extAuditId = saveExtAuditInfo(task, data);
                // 移除老待办
                removeExtTodoInfo(extAuditId);
            } else {
                data.setWorkFlowId(pi.getId());
                data.setAuditFlag(AuditFlagEnum.AUDIT.getVal());
                // 插入数据审核状态表
                BigInteger extAuditId = saveExtAuditInfo(task, data);
                Task flowTask = selectSigleTask(pi.getId());
                // 转换业务系统的待办任务
                List<UserInfo> userList = doWithTodoInfo(flowTask, data.getOwnDeptId(), task.getDeptId());
                // 移除老待办
                removeExtTodoInfo(extAuditId);
                // 插入代办任务表
                insertExtTodoInfo(extAuditId, userList, task.getUserId());
            }
            // 插入审核日志表
            insertAuditLog(task, data);
            progress.setSuccessNum(atom.incrementAndGet());
            cacheService.store(progress.getId(), progress, 120);
        }
        sendTopicInfo(task);
        return dataList;
    }

    @Override
    public IPage<ActExtAuditVO> selectTodoPage(PageExt<ActExtAuditVO> queryInfo) {
        ActExtAuditVO query = queryInfo.getQueryObject();
        IPage<ActExtAuditVO> pageResult = actExtTodoMapper.queryTodoPage(queryInfo, query);
        if (CollUtil.isNotEmpty(pageResult.getRecords())) {
            for (ActExtAuditVO it : pageResult.getRecords()) {
                it.setAuditName(AuditFlagEnum.getNameByVal(it.getAuditFlag()));
            }
        }
        return pageResult;
    }

    @Override
    public List<AuditData> skipTask(AuditTask task) {
        List<AuditData> dataList = task.getDataList();
        ProgressInfo progress = new ProgressInfo(task.getTaskId(), dataList.size());
        cacheService.store(progress.getId(), progress, 600);
        for (AuditData data : dataList) {
            try {
                String flowId = data.getWorkFlowId();
                if (StringUtils.isEmpty(flowId)) {
                    throw new ServiceException("flow skip task error: data.workFlawId is empty");
                }
                DecisionEnum decision = DecisionEnum.get(task.getDecision());
                if (decision == null) {
                    throw new ServiceException("flow skip task error: not support decision");
                }
                log.info("skipTask: {}", decision.desc());
                switch (decision) {
                    case REJECT:
                        doReject(task, data, flowId);
                        break;
                    case PASS:
                        doPass(task, data, flowId);
                        break;
                    case TRAN:
                        doTran(task, data, flowId);
                        break;
                    case REVOKE:
                        doRevoke(task, data, flowId);
                        break;
                    case DONE:
                        doDone(task, data, flowId);
                        break;
                    default:
                        throw new ServiceException("flow skip task error: not support decision");
                }
                progress.setSuccessNum(progress.getSuccessNum() + 1);
            } catch (Exception ex) {
                log.error("flow skip error", ex);
                progress.setFailNum(progress.getFailNum() + 1);
            } finally {
                cacheService.store(progress.getId(), progress, 120);
            }
        }
        sendTopicInfo(task);
        return dataList;
    }

    @Tran(policy = TranPolicy.required)
    @Override
    public void doReject(AuditTask task, AuditData data, String flowId) {
        data.setAuditFlag(AuditFlagEnum.REJECT.getVal());
        data.setWorkFlowId(null);

        Map<String, Object> valuemap = new HashMap<>();
        valuemap.put(ExtConstants.AUDITOR, task.getUserId());
        valuemap.put(ExtConstants.OPINION, task.getMessage());
        valuemap.put(ExtConstants.DECISION, DecisionEnum.REJECT.code());
        runtimeService.setVariables(flowId, valuemap);
        runtimeService.deleteProcessInstance(flowId, task.getMessage());

        // 更新数据审核状态表
        BigInteger extAuditId = saveExtAuditInfo(task, data);
        removeExtTodoInfo(extAuditId);
        // 插入审核日志表
        insertAuditLog(task, data);
    }

    @Tran(policy = TranPolicy.required)
    @Override
    public void doPass(AuditTask task, AuditData data, String flowId) {
        Task oldTask = selectSigleTask(flowId);
        Map<String, Object> startVar = runtimeService.getVariables(flowId);
        startVar.put(ExtConstants.AUDITOR, task.getUserId());
        startVar.put(ExtConstants.OPINION, task.getMessage());
        startVar.put(ExtConstants.DECISION, DecisionEnum.PASS.code());
        // 使用启动参数，完成老任务
        taskService.complete(oldTask.getId(), startVar);
        // 更新数据审核状态表
        ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(flowId).singleResult();
        if (pi == null || pi.isEnded()) {
            // 审核结束，待发布
            data.setAuditFlag(AuditFlagEnum.DEPLOY.getVal());
            BigInteger extAuditId = saveExtAuditInfo(task, data);
            // 移除待办
            removeExtTodoInfo(extAuditId);
        } else {
            // 继续审核中
            data.setAuditFlag(AuditFlagEnum.AUDIT.getVal());
            Task flowTask = selectSigleTask(pi.getId());
            BigInteger extAuditId = saveExtAuditInfo(task, data);
            // 转换业务系统的待办任务
            List<UserInfo> userList = doWithTodoInfo(flowTask, data.getOwnDeptId(), task.getDeptId());
            // 移除老待办
            removeExtTodoInfo(extAuditId);
            // 插入代办任务表
            insertExtTodoInfo(extAuditId, userList, task.getUserId());
        }
        // 插入审核日志表
        insertAuditLog(task, data);
    }

    @Tran(policy = TranPolicy.required)
    @Override
    public void doTran(AuditTask task, AuditData data, String flowId) {
        log.info("tran: {}", flowId);
        // 把当前人的待办换成目标用户
        // 插入审核日志表
        insertAuditLog(task, data);
    }

    @Tran(policy = TranPolicy.required)
    @Override
    public void doRevoke(AuditTask task, AuditData data, String flowId) {
        log.info("revoke: {}", flowId);

        Map<String, Object> valuemap = new HashMap<>();
        valuemap.put(ExtConstants.AUDITOR, task.getUserId());
        valuemap.put(ExtConstants.OPINION, "revoke");
        valuemap.put(ExtConstants.DECISION, DecisionEnum.REVOKE.code());
        runtimeService.setVariables(flowId, valuemap);
        runtimeService.deleteProcessInstance(flowId, "revoke");
        // 更新数据审核状态表
        BigInteger extAuditId = saveExtAuditInfo(task, data);
        removeExtTodoInfo(extAuditId);
        // 插入审核日志表
        insertAuditLog(task, data);
    }

    @Tran(policy = TranPolicy.required)
    @Override
    public void doDone(AuditTask task, AuditData data, String flowId) {
        log.info("done: {}", flowId);

        Map<String, Object> valuemap = new HashMap<>();
        valuemap.put(ExtConstants.AUDITOR, task.getUserId());
        valuemap.put(ExtConstants.OPINION, task.getMessage());
        valuemap.put(ExtConstants.DECISION, DecisionEnum.DONE.code());
        runtimeService.setVariables(flowId, valuemap);
        runtimeService.deleteProcessInstance(flowId, task.getMessage());
        // 更新数据审核状态表
        BigInteger extAuditId = saveExtAuditInfo(task, data);
        removeExtTodoInfo(extAuditId);
        // 插入审核日志表
        insertAuditLog(task, data);
    }

    @Override
    public List<ActExtFlowLogVO> selectOpinionList(ActExtAuditVO queryInfo) {
        List<ActExtFlowLogVO> list = actExtFlowLogMapper.selectOpinionList(queryInfo);
        for (ActExtFlowLogVO flowLog : list) {
            flowLog.setAuditName(AuditFlagEnum.getNameByVal(flowLog.getAuditFlag()));
        }
        return list;
    }

    private Task selectSigleTask(String flowId) {
        // 获取执行任务的人
        List<Task> flowTaskList = taskService.createTaskQuery().processInstanceId(flowId).list();
        // 会签多个任务，不支持
        if (CollUtil.isEmpty(flowTaskList) || CollUtil.size(flowTaskList) > 1) {
            throw new ServiceException("flow design error: not support Empty task or ParallelGateway");
        }
        return flowTaskList.get(0);
    }

    /**
     * 插入数据审核状态表
     */
    private BigInteger saveExtAuditInfo(AuditTask task, AuditData data) {
        LambdaQueryWrapper<ActExtAudit> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ActExtAudit::getAreaCode, task.getAreaCode());
        queryWrapper.eq(ActExtAudit::getDataType, task.getDataType());
        queryWrapper.eq(ActExtAudit::getDataId, data.getDataId());
        ActExtAudit auditInfo = actExtAuditMapper.selectOne(queryWrapper);
        if (auditInfo == null) {
            ActExtAudit newInfo = buildActExtAudit(task, data);
            actExtAuditMapper.insert(newInfo);
            return newInfo.getUid();
        } else {
            ActExtAudit update = new ActExtAudit();
            update.setUid(auditInfo.getUid());
            update.setTaskId(task.getTaskId());
            update.setWorkFlowId(data.getWorkFlowId());
            update.setAuditFlag(data.getAuditFlag());
            update.setUpdateBy(task.getUserId());
            update.setUpdateTime(new Date());
            actExtAuditMapper.updateById(update);
            return update.getUid();
        }
    }

    private ActExtAudit buildActExtAudit(AuditTask task, AuditData data) {
        long uid = IdUtil.getSnowflakeNextId();
        ActExtAudit auditInfo = new ActExtAudit();
        auditInfo.setUid(BigInteger.valueOf(uid));
        auditInfo.setAreaCode(task.getAreaCode());
        auditInfo.setDataType(task.getDataType());
        auditInfo.setTaskId(task.getTaskId());
        auditInfo.setDataId(data.getDataId());
        auditInfo.setWorkFlowId(data.getWorkFlowId());
        auditInfo.setAuditFlag(data.getAuditFlag());
        auditInfo.setDataDesc(data.getDataDesc());

        auditInfo.setCreateBy(task.getUserId());
        auditInfo.setUpdateBy(task.getUserId());
        auditInfo.setCreateTime(new Date());
        auditInfo.setUpdateTime(new Date());
        return auditInfo;
    }

    /**
     * 处理工作流任务，生成代办
     *
     * @param flowTask 工作流任务
     * @param ownDeptId 提报单位
     * @param actDeptId 操作人单位
     * @return 审核人列表
     */
    private List<UserInfo> doWithTodoInfo(Task flowTask, String ownDeptId, String actDeptId) {
        String assignee = flowTask.getAssignee();
        String description = flowTask.getDescription();
        log.info("id:{} as:{} desc:{}", flowTask.getName(), assignee, description);
        Map<String, String> extDesc = parseDescription(description);
        String relation = extDesc.get("relation");
        // 是人、部门、角色
        List<UserInfo> userList = analyseAssignee(assignee);
        if (StringUtils.equals(relation, "1")) {
            log.info("同提报单位:{}", ownDeptId);
            userList = userList.stream()
                    .filter(it -> StringUtils.equals(it.getDeptId(), ownDeptId))
                    .collect(Collectors.toList());
        } else if (StringUtils.equals(relation, "2")) {
            log.info("同操作人单位:{}", actDeptId);
            userList = userList.stream()
                    .filter(it -> StringUtils.equals(it.getDeptId(), actDeptId))
                    .collect(Collectors.toList());
        }
        return userList;
    }

    /**
     * 插入代办
     */
    private void insertExtTodoInfo(BigInteger extAuditId, List<UserInfo> userList, String userId) {
        for (UserInfo userInfo : userList) {
            ActExtTodo extTodo = new ActExtTodo();
            extTodo.setPid(BigInteger.valueOf(IdUtil.getSnowflakeNextId()));
            extTodo.setPid(extAuditId);
            extTodo.setUserId(userInfo.getUserId());
            extTodo.setUserName(userInfo.getUserName());
            extTodo.setDeptId(userInfo.getDeptId());
            extTodo.setCreateBy(userId);
            extTodo.setUpdateBy(userId);
            extTodo.setCreateTime(new Date());
            extTodo.setUpdateTime(new Date());
            actExtTodoMapper.insert(extTodo);
        }
    }

    /**
     * 移除代办
     */
    private void removeExtTodoInfo(BigInteger extAuditId) {
        LambdaQueryWrapper<ActExtTodo> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ActExtTodo::getPid, extAuditId);
        actExtTodoMapper.delete(queryWrapper);
    }

    /**
     * 插入流程日志
     */
    private void insertAuditLog(AuditTask task, AuditData data) {
        LambdaQueryWrapper<ActExtFlowLog> updateWrapper = new LambdaQueryWrapper<>();
        updateWrapper.eq(ActExtFlowLog::getAreaCode, task.getAreaCode());
        updateWrapper.eq(ActExtFlowLog::getDataType, task.getDataType());
        updateWrapper.eq(ActExtFlowLog::getDataId, data.getDataId());
        updateWrapper.eq(ActExtFlowLog::getTopFlag, 1);
        ActExtFlowLog update = new ActExtFlowLog();
        update.setTopFlag(0);
        update.setUpdateBy(task.getUserId());
        update.setUpdateTime(new Date());
        actExtFlowLogMapper.update(update, updateWrapper);

        ActExtFlowLog flowLog = new ActExtFlowLog();
        flowLog.setUid(BigInteger.valueOf(IdUtil.getSnowflakeNextId()));
        flowLog.setAreaCode(task.getAreaCode());
        flowLog.setDataType(task.getDataType());
        flowLog.setDataId(data.getDataId());
        flowLog.setWorkFlowId(data.getWorkFlowId());
        flowLog.setAuditFlag(data.getAuditFlag());
        flowLog.setDecisionName(DecisionEnum.getDescByCode(task.getDecision()));
        flowLog.setMessage(task.getMessage());
        flowLog.setTopFlag(1);
        flowLog.setCreateBy(task.getUserId());
        flowLog.setCreateTime(new Date());
        flowLog.setUpdateBy(task.getUserId());
        flowLog.setUpdateTime(new Date());
        actExtFlowLogMapper.insert(flowLog);
    }

    private List<UserInfo> analyseAssignee(String assignee) {
        if (StringUtils.isEmpty(assignee)) {
            throw new ServiceException("flow design error: Please select a assignee");
        }
        Pattern pattern = Pattern.compile("\\{([^}]+)}");
        Matcher matcher = pattern.matcher(assignee);
        if (matcher.find()) {
            String asCode = matcher.group(1);
            List<UserInfo> userList = new ArrayList<>();
            if (StringUtils.startsWith(assignee, "#USER")) {
                UserInfo user = actUserService.selectUserByName(asCode);
                userList.add(user);
            } else if (StringUtils.startsWith(assignee, "#ROLE")) {
                List<UserInfo> list = actUserService.selectUserByRole(asCode);
                userList.addAll(list);
            } else if (StringUtils.startsWith(assignee, "#GROUP")) {
                List<UserInfo> list = actUserService.selectUserByGroup(asCode);
                userList.addAll(list);
            }
            return userList;
        }
        throw new ServiceException("flow design error: assignee is not support");
    }

    /**
     * 解析扩展参数
     * 传入参数: str 形如  "ccs="#USER{}";editable="0";"
     */
    private Map<String, String> parseDescription(String str) {
        Map<String, String> result = new HashMap<>();
        String[] keyValuePairs = str.split(";"); // 分割成键值对
        for (String pair : keyValuePairs) {
            String[] entry = pair.split("="); // 分割键和值
            String key = entry[0].trim(); // 去除空白字符
            String value = entry[1].trim(); // 去除空白字符
            // 移除键值对的引号
            if (value.startsWith("\"") && value.endsWith("\"")) {
                value = value.substring(1, value.length() - 1);
            }
            result.put(key, value);
        }
        return result;
    }

    private void sendTopicInfo(AuditTask task) {
        RedisBus bus = redisClient.getBus();
        bus.publish("flowable", ONode.stringify(task));
    }
}
